大家應該都碰過處理文件的需求,不管是上傳圖片、讀取電腦的文件或是從網站下載文件... 等,多少都已經和 File API 打過交道了。今天想跟大家分享的是 File API 的核心概念,還有 FileReader、Blob 物件的功用,以及一些實際範例。
File API 可以讓網頁讀寫我們本地端的文件,也能對文件進行操作,且無須伺服器端的介入。Web API 包含以下幾個部分:
我們可以透過 <input type="file">
處理上傳的文件,並取得 File 的物件屬性。以下是 File 的主要屬性:
name
:文件名稱size
:文件大小 (單位: Byte)type
:文件類型lastModified
:最後修改時間上傳文件的按鈕
<input type="file" id="fileInput" />
上傳文件後,使用 event.target.files
得到文件的相關資訊
const fileInput = document.getElementById('fileInput');
fileInput.addEventListener('change', (event) => {
const file = event.target.files[0];
console.log(file);
});
這就是我們取得的內容
FileReader 顧名思義,就是用來讀文件的,他可以將文件轉為 Text、ArrayBuffer,或是 Data Url,以下是主要方法:
readAsText()
:將文件讀取為 TextreadAsArrayBuffer()
:將文件讀取為 ArrayBufferreadAsDataURL()
:將文件讀取為 Data URLabort()
:中止讀取操作我們先建立一個 FileReader
const reader = new FileReader()
再用這個 reader
來讀取文件,並轉成想要的格式。
const fileInput = document.getElementById('fileInput');
const reader = new FileReader();
fileInput.addEventListener('change', (event) => {
const file = event.target.files[0];
// 用你想要的讀取方法,來讀取 file
reader.readAsText(file);
});
reader.onload = (event) => {
console.log('文件', event.target.result);
};
reader.onerror = (error) => {
console.error('讀取文件失敗', error);
};
readAsText()
適合讀取與處理文件,例如 CSV、JSON、XML 和 TXT...等等
⬇️ reader.readAsText(file)
可以再將讀取來的資料進一步解析
reader.onload = (event) => {
console.log('文件', event.target.result);
const text = event.target.result;
console.log('文件內容', text);
// 解析 JSON 文件
try {
const data = JSON.parse(text);
console.log('解析後的數據', data);
} catch (error) {
console.error('無法解析 JSON 文件', error);
}
}
ArrayBuffer 是二進制的數據結構,他有點像是「容器」,用來儲存二進制原始數據,如果有需要,我們可以用一些方法(如 DataView) 來操作這些數據。
⬇️ reader.readAsArrayBuffer(file)
假設我想要偵測使用者上傳的圖片格式為何?這時候就能利用 DavaView
來操作 ArrayBuffer()
取得資料。
先寫一個檢查圖片格式的函式
function checkImageFormat(view) {
// JPEG 文件的前兩個 byte 應該是 0xFFD8
const jpegMarker = view.getUint16(0);
if (jpegMarker === 0xFFD8) {
return 'JPEG';
}
// GIF 文件的前兩個字節應該是 0x4749
const gifMarker = view.getUint16(0);
if (gifMarker === 0x4749) {
return 'GIF';
}
// PNG 文件的前八個字節應該是 0x89504E470D0A1A0A
const pngMarker = view.getUint32(0) === 0x89504E47 && view.getUint32(4) === 0x0D0A1A0A;
if (pngMarker) {
return 'PNG';
}
return '未知格式';
}
在 reader.onload()
呼叫函式,並顯示回傳結果
reader.onload = (event) => {
const arrayBuffer = event.target.result;
// 建立 DataView 實例
const view = new DataView(arrayBuffer)
// 使用 checkImageFormat() 取得圖片格式
const format = checkImageFormat(view);
console.log('圖片格式:', format);
}
上傳圖片後,就可以在 console 取得圖片的格式了
readAsDataURL()
在處理上傳圖片時很常用到。它可以將圖片轉換成 base64 格式,並直接在網頁上顯示圖片,而不需要讀取圖片的 URL。這樣不需等待圖片上傳或從伺服器讀取,顯示速度更快且更安全。
⬇️ reader.readAsDataUrl(file)
接著是 Web API 的第三個部分:Blob。Blob (Binary Large Object) 代表一個不可變的、原始資料的類似檔案的物件。可以用來存放圖片、影片等多種格式的資料,並且可以用多種方法進行操作。我們通常會用 Blob 處理二進制數據以及建立新的文件。
Blob 有兩個常用的方法:
slice()
:建立一個新的 Blob 物件stream()
:返回一個 ReadableStream,用於讀取 Blob 內容以下是使用這兩個方法的程式碼範例
const fileInput = document.getElementById('fileInput');
fileInput.addEventListener('change', async function (event) {
const file = event.target.files[0];
const reader = new FileReader();
reader.readAsDataURL(file);
// 使用 Blob 來處理文件
const blob = new Blob([file], { type: file.type });
console.log('blob', blob)
// 使用 slice() 方法
const sliceBlob = blob.slice(0, blob.size / 2);
console.log('sliceBlob', sliceBlob);
// 使用 stream() 方法
const stream = blob.stream();
const streamReader = stream.getReader();
const { value } = await streamReader.read();
console.log('Stream chunk:', value);
});
線上範例網址:https://mukiwu.github.io/web-api-demo/file-blob.html
我們可以用 URL.createObjectURL()
方法建立一個特定物件的 URL,用於在網站顯示本機圖片,或提供文件下載。
前面段落分享了使用 Web API 建立 File 或 Blob Object,而 URL.createObjectURL()
的用途,就是能幫這些 File / Blog Object 產生一個臨時的 URL,用於 HTML DOM,例如 <img>
或 <a>
,以便在網頁上顯示本地圖片或提供文件下載。這種方式避免了將文件直接上傳到伺服器,可以加快響應速度,也能減輕伺服器負擔。
最常見的一個實作,就是在使用者上傳圖片時,顯示圖片的預覽畫面。
首先準備一個 <input type="file />
以及一個 <img />
的預覽框
<input type="file" id="fileInput" />
<img id="preview" src="" alt="Image preview" style="display:none;" />
上傳圖片後,用 createObjectURL()
產生一個臨時的 url,並且用 <img src="" />
引入這個 url
const fileInput = document.getElementById('fileInput');
const img = document.getElementById('preview');
fileInput.addEventListener('change', async function (event) {
const file = event.target.files[0];
const imgURL = URL.createObjectURL(file);
// 將 createObjectURL 產生的 URL 放入 <img />
img.src = imgURL;
img.style.display = 'block';
});
如此就能在上傳後,直接顯示預覽的圖片,而且不需要經過後端伺服器唷
線上範例網址:https://mukiwu.github.io/web-api-demo/file-objurl.html
相信大家對 File API 的構造與方法有了一定的了解,接下來分享幾個我們比較常用的範例,整合了前面段落提到的各種方法,相信大家接下來再看這些程式碼,會更了解他們的用途。
常見的做法是使用「chunk」概念,將大檔案拆分成一個個小區塊,然後分批上傳到伺服器。這種方法可以加快響應時間,並降低上傳失敗的風險。
我們可以使用 Blob.slice()
幫我們分割檔案,並逐塊上傳
// 設定 chunk 的大小是 1MB,也就是每 1MB 會拆成一個小塊
const CHUNK_SIZE = 1024 * 1024;
const fileInput = document.getElementById('fileInput');
fileInput.addEventListener('change', async function (event) {
const file = event.target.files[0];
uploadFile(file)
});
// 重點函式
function uploadFile(file) {
const totalChunks = Math.ceil(file.size / CHUNK_SIZE);
let currentChunk = 0;
function uploadNextChunk() {
const start = currentChunk * CHUNK_SIZE;
const end = Math.min(file.size, start + CHUNK_SIZE);
const chunk = file.slice(start, end);
const formData = new FormData();
formData.append('file', chunk, file.name);
formData.append('chunk', currentChunk);
formData.append('totalChunks', totalChunks);
// 模擬上傳
setTimeout(() => {
console.log(`上傳 chunk ${currentChunk + 1}/${totalChunks}:`, chunk);
currentChunk++;
if (currentChunk < totalChunks) {
uploadNextChunk();
} else {
console.log('文件上傳完成');
}
}, 500); // 模擬網絡延遲
}
uploadNextChunk();
}
可以透過 console.log()
看到上傳的檔案大小與總數量
也能不經由伺服器,直接透過網站讓使用者下載文件,使用 Blob()
以及 createObjectURL()
<button onclick="generateAndDownloadFile()">下載檔案</button>
function generateAndDownloadFile() {
const content = '這是你的文件內容:HELLO, world.';
const blob = new Blob([content], { type: 'text/plain' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'download.txt';
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
}
我將範例一和範例二合併在同一份檔案中。
這是線上範例網址:https://mukiwu.github.io/web-api-demo/file-objurl.html
還記得我們在前面的文章有分享過 Drag and Drop API
嗎?我們還能結合這個 API 做到拖放上傳檔案的功能唷!
<style>
img {
max-width: 100%;
}
.drag-over-yellow {
background-color: yellow;
}
.drop-zone {
height: 300px;
width: 300px;
border: 1px solid black;
margin: 10px;
padding: 10px;
}
</style>
<div id="drop-zone" class="drop-zone">放置區域</div>
監聽 drop
事件,將處理檔案的邏輯寫在裡面
const dropZone = document.getElementById('drop-zone');
dropZone.addEventListener('dragover', (event) => {
event.preventDefault();
});
dropZone.addEventListener('dragenter', (event) => {
// 可以多加一層判斷,如果是檔案,拖曳區域才會變色
if (event.dataTransfer.items && event.dataTransfer.items[0].kind === 'file') {
event.target.classList.add('drag-over-yellow');
}
});
dropZone.addEventListener('dragleave', (event) => {
event.target.classList.remove('drag-over-yellow');
});
dropZone.addEventListener('drop', (event) => {
event.preventDefault();
event.target.classList.remove('drag-over-yellow');
// 取得 file 後,再用 createObjectURL() 顯示預覽圖片
const file = event.dataTransfer.files[0];
if (file) {
const imgURL = URL.createObjectURL(file);
const img = document.createElement('img');
img.src = imgURL;
dropZone.innerHTML = '';
dropZone.appendChild(img);
}
});
線上範例網址:https://mukiwu.github.io/web-api-demo/file-drag.html
原本以為 File API 相對簡單,篇幅應該很短,沒想到寫完居然爆字數 XDD。
也希望透過這篇的介紹,能讓不熟悉的朋友,對 File API 有進一步的了解,有任何問題都歡迎留言討論唷。